package codename.carrot.bomberman.controllers;

import java.util.ArrayList;

import sheep.gui.WidgetAction;
import sheep.gui.WidgetListener;
import android.util.Log;
import android.widget.Toast;
import codename.carrot.bomberman.AppStorage;
import codename.carrot.bomberman.AppStorage.Direction;
import codename.carrot.bomberman.game.gamemessages.PlayerMovementMessage;
import codename.carrot.bomberman.game.listener.PlayerListener;
import codename.carrot.bomberman.game.listener.PlayerPositionListener;
import codename.carrot.bomberman.models.AbstractObstacle;
import codename.carrot.bomberman.models.CollectableItem;
import codename.carrot.bomberman.models.Flame;
import codename.carrot.bomberman.models.ItemContainer;
import codename.carrot.bomberman.models.Level;
import codename.carrot.bomberman.models.Player;
import codename.carrot.bomberman.network.client.ClientPostOffice;
import codename.carrot.bomberman.views.widgets.JoysticWidgetAction;

/**
 * Controls player movement for local player, as well as processing events
 * coming from the server, regarding remote players, during a network-game (via
 * the PlayerListener interface)
 * 
 * PlayerPositionListener is implemented to improve local control of player
 * movement
 * 
 * @author Group A4
 * 
 */
public class PlayerController implements PlayerListener, WidgetListener,
		PlayerPositionListener {
	
	/**
	 * ArrayList of in-game players.
	 */
	private ArrayList<Player> players;
	/**
	 * The local player.
	 */
	private Player localPlayer;
	/**
	 * The current level.
	 */
	private Level level;

	/**
	 * Constructs the PlayerController.
	 * @param level The current level.
	 * @param localPlayer The local player.
	 * @param players List of activ in-game players.
	 */
	public PlayerController(Level level, Player localPlayer, ArrayList<Player> players) {
		this.localPlayer = localPlayer;
		this.players = players;
		this.level = level;
		// We need to know exactly what is going on with the local player in
		// terms of
		// movement. How fast the player moves, etc, is based on player
		// attributes.
		// by adding this PlayerController to localPlayer as
		// playerPositionListener,
		// we will be notified immediately when the player starts moving, stops
		// moving,
		// as well as when the center of the player has moved to a new tile.
		this.localPlayer.addPlayerPositionListener(this);
	}

	/**
	 * A player has started moving from a specific coordinate, in a specific
	 * direction
	 */
	@Override
	public void onPlayerMoved(int playerId, int xCoorMapFrom, int yCoorMapFrom,
			Direction direction) {
		Player p = getPlayer(playerId);
		if (p != null && localPlayer.getPlayerId() != playerId) {
			p.cancelMoving();
			p.setMapPosition(xCoorMapFrom, yCoorMapFrom);
			p.updateDirection(direction);
			p.startMoving();
		}
	}

	/**
	 * Returns the player, given the player id.
	 * @param playerId The ID of the player to fetch.
	 * @return The Player.
	 */
	private Player getPlayer(int playerId) {
		for (Player p : players) {
			if (p.getPlayerId() == playerId)
				return p;
		}
		return null;
	}

	/**
	 * A player has stopped moving ( and landed in a specific coordinate )
	 */
	@Override
	public void onPlayerStopsMoving(int playerId, int xCoorMap, int yCoorMap) {
		Player p = getPlayer(playerId);
		if (p != null && localPlayer.getPlayerId() != playerId) {
			p.cancelMoving();
			p.setMapPosition(xCoorMap, yCoorMap);
		}
	}

	/**
	 * Local player has touched the joystick ( time to handle the event )
	 */
	@Override
	public void actionPerformed(WidgetAction action) {
		if (localPlayer.isDead())
			return;
		if (action instanceof JoysticWidgetAction) {
			// What's the state of the Widget right now?
			// LETS FIGURE THE FUCK OUT
			JoysticWidgetAction ja = (JoysticWidgetAction) action;
			switch (ja.getState()) {
			case START:
				Direction direction = ja.getDirection();
				switch (direction) {
				case LEFT_DOWN:
					if (canPlayerMove(Direction.LEFT, localPlayer))
						direction = Direction.LEFT;
					else
						direction = Direction.DOWN;
					break;
				case LEFT_UP:
					if (canPlayerMove(Direction.LEFT, localPlayer))
						direction = Direction.LEFT;
					else
						direction = Direction.UP;
					break;
				case RIGHT_DOWN:
					if (canPlayerMove(Direction.RIGHT, localPlayer))
						direction = Direction.RIGHT;
					else
						direction = Direction.DOWN;
					break;
				case RIGHT_UP:
					if (canPlayerMove(Direction.RIGHT, localPlayer))
						direction = Direction.RIGHT;
					else
						direction = Direction.UP;
					break;
				default:
					break;
				}
				if (canPlayerMove(direction, localPlayer)) {
					// Send message to server about movement
					if (direction != localPlayer.getDirection()) {
						localPlayer.updateDirection(direction);
					}
					localPlayer.startMoving();
				} else {
					localPlayer.stopMoving();
				}
				break;
			case STOP:
				localPlayer.stopMoving();
				break;
			}
		}
	}

	/**
	 * Check if the player can move in a given direction
	 * @param direction The direction to check.
	 * @param player The player that is moving.
	 * @return True if the player can move or false otherwise.
	 */
	private boolean canPlayerMove(Direction direction, Player player) {
		int xCoorObstacleCheck = player.getXcoorMap();
		int yCoorObstacleCheck = player.getYcoorMap();
		switch (direction) {
		case UP:
			yCoorObstacleCheck--;
			Log.d("DROIDMOVEMENT", direction.toString());
			break;
		case DOWN:
			yCoorObstacleCheck++;
			Log.d("DROIDMOVEMENT", direction.toString());
			break;
		case LEFT:
			xCoorObstacleCheck--;
			Log.d("DROIDMOVEMENT", direction.toString());
			break;
		case RIGHT:
			xCoorObstacleCheck++;
			Log.d("DROIDMOVEMENT", direction.toString());
			break;
		default:
			return false;
		}
		if (xCoorObstacleCheck < 0
				|| xCoorObstacleCheck >= (level.getWidth() / AppStorage.TILE_SIZE))
			return false;
		if (yCoorObstacleCheck < 0
				|| yCoorObstacleCheck >= (level.getHeight() / AppStorage.TILE_SIZE))
			return false;

		if (level.getObstacle(xCoorObstacleCheck, yCoorObstacleCheck) instanceof Flame) {
			// player can walk trough flames
			return true;
		}
		return level.getObstacle(xCoorObstacleCheck, yCoorObstacleCheck) == null;
	}

	/**
	 * A player has picked up an item
	 */
	@Override
	public void onPlayerItemPickup(int playerId, int xCoorMap, int yCoorMap) {
		Player p = getPlayer(playerId);

		// Update stats if local player picked up an item
		if (playerId == localPlayer.getPlayerId()) {
			AppStorage.droidPref.addItemsCollected();
		}
		// Check if the item is available in the ItemList.. if not, it might be
		// in a crate that has not yet exploded
		CollectableItem item = level.getItem(xCoorMap, yCoorMap);
		if (item != null) {
			p.pickedUp(item);
			level.removeItem(item);
		} else {
			// Maybe (for some magical random reason, which is impossible to
			// understand for mortal people, the crate
			// containing this item, has not yet exploded... and we need to get
			// the item from the container itself
			AbstractObstacle obstacle = level.getObstacle(xCoorMap, yCoorMap);
			if (obstacle != null && obstacle instanceof ItemContainer) {
				ItemContainer itemContainer = (ItemContainer) obstacle;
				if (itemContainer.hasItem())
					p.pickedUp(itemContainer.getItem());
				// Remove the item from the itemcontainer
				itemContainer.setItem(null);
			}
		}
	}

	@Override
	public void onPlayerLostLife(int playerVictimId, int playerKillerId,
			int livesLeft) {
		Player victim = getPlayer(playerVictimId);
		if (livesLeft <= 0) {
			victim.setIsDead(true);
			Toast.makeText(AppStorage.gameActivity, victim.getNickname() + " is no more...", Toast.LENGTH_SHORT).show();
		}
		victim.setLife(livesLeft);
		//if local player hurt himself (victim AND killer)
		if (victim.getPlayerId() == localPlayer.getPlayerId() && playerKillerId == localPlayer.getPlayerId()){
			victim.firePlayerLostLife();
			AppStorage.droidPref.addSelfLifeTaken(1);
		}
		//if the local player is the victim
		if (victim.getPlayerId() == localPlayer.getPlayerId()) {
			victim.firePlayerLostLife();
			AppStorage.droidPref.addLifeLost(1);
		}
		
		//if the local player is the killer
		if (victim.getPlayerId() == playerKillerId  && victim.getPlayerId() != localPlayer.getPlayerId() ) {
			AppStorage.droidPref.addLifeTaken(1);
		}
	}

	/**
	 * Notification that a player has moved to a new x-coordinate on the map
	 */
	@Override
	public void onXcoorGridChanged(int xCoorGrid, Player player) {
		if (player.getPlayerId() == localPlayer.getPlayerId()) {
			if (player.isMoving()) {
				if (!canPlayerMove(localPlayer.getDirection(), player)) {
					player.stopMoving();
				}
			}
		}
	}

	/**
	 * Notification that a player has moved to a new y-coordinate on the map
	 */
	@Override
	public void onYCoorGridChanged(int yCoorGrid, Player player) {
		if (player.getPlayerId() == localPlayer.getPlayerId()) {
			if (player.isMoving()) {
				if (!canPlayerMove(localPlayer.getDirection(), player)) {
					player.stopMoving();
				}
			}
		}
	}

	/**
	 * Notification that a player has finished moving
	 */
	@Override
	public void onMovementEnds(int xCoorGrid, int yCoorGrid, Player player) {
		if (player.getPlayerId() == localPlayer.getPlayerId()) {
			// Send stop message
			PlayerMovementMessage pm = new PlayerMovementMessage(
					localPlayer.getPlayerId(), xCoorGrid, yCoorGrid,
					localPlayer.getDirection().ordinal(), false);
			ClientPostOffice.getInstance().sendMessage(pm);
		}
	}
	
	/**
	 * Notification that a player has started moving
	 */
	@Override
	public void onMovementBegins(int xCoorGrid, int yCoorGrid, Player player) {
		PlayerMovementMessage pm = new PlayerMovementMessage(
				localPlayer.getPlayerId(), xCoorGrid, yCoorGrid, localPlayer
						.getDirection().ordinal(), true);
		ClientPostOffice.getInstance().sendMessage(pm);
	}

    @Override
    public void onPlayerQuit(int playerId) {
        Player p = getPlayer(playerId);
        if(p != null) {
            p.setIsDead(true);
        }
    }
}
